package com.tianji.promotion.service.impl;

import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.api.cache.CategoryCache;
import com.tianji.api.dto.course.CategoryBasicDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.enums.CouponStatus;
import com.tianji.promotion.domain.enums.ObtainType;
import com.tianji.promotion.domain.enums.UserCouponStatus;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponScopeVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.tianji.promotion.domain.enums.CouponStatus.PAUSE;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author author
 * @since 2024-11-20
 */
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {
    private final ICouponScopeService couponScopeService;
    private final IExchangeCodeService exchangeCodeService;
    private final CategoryCache categoryCache;
    private final IUserCouponService userCouponService;

    /**
     * 新增优惠券
     *
     * @param dto
     */
    @Override
    public void saveCoupon(CouponFormDTO dto) {
        //1. 数据拷贝
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        //2.保存进数据库
        save(coupon);
        //3.判断是否有限定范围
        if (!dto.getSpecific()) {
            return;
        }
        //4.保存限定范围
        //4.1获取id
        Long couponId = coupon.getId();
        //4.2收集限定范围
        List<Long> scopes = dto.getScopes();
        List<CouponScope> collect = scopes.stream().map(scope -> new CouponScope().setBizId(scope)
                .setCouponId(couponId)).collect(Collectors.toList());

        if (CollectionUtils.isEmpty(scopes)) {
            throw new BadRequestException("优惠券限定范围不能为空");
        }
        //4.3保存进数据库
        couponScopeService.saveBatch(collect);
    }

    /**
     * 分页查询优惠券
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<CouponPageVO> queryCouponByPage(CouponQuery query) {
        //1.条件查询
        Page<Coupon> page = this.lambdaQuery()
                .eq(query.getStatus() != null, Coupon::getStatus, query.getStatus())
                .eq(query.getType() != null, Coupon::getType, query.getType())
                .like(StringUtils.isNotBlank(query.getName()), Coupon::getName, query.getName())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        if (CollUtils.isEmpty(page.getRecords())) {
            return PageDTO.empty(page);
        }
        //2.数据拷贝
        List<CouponPageVO> list = BeanUtils.copyList(page.getRecords(), CouponPageVO.class);
        return PageDTO.of(page, list);
    }

    /**
     * 新增优惠券
     *
     * @param dto
     */
    @Override
    public void beginIssue(CouponIssueFormDTO dto) {
        //1.查询优惠券
        Coupon coupon = this.getById(dto.getId());
        if (ObjectUtils.isEmpty(coupon)) {
            throw new BadRequestException("优惠券不存在");
        }

        //2.判断优惠券状态
        //判断状态不是待发放和暂停
        if (coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != PAUSE) {
            throw new BadRequestException("优惠券状态不正确");
        }

        //3.判断是否立即发放优惠券
        //3.1先拷贝属性
        Coupon coupon1 = BeanUtils.copyBean(dto, Coupon.class);
        LocalDateTime beginTime = dto.getIssueBeginTime();
        if (beginTime == null || beginTime.isBefore(LocalDateTime.now())) {
            beginTime = LocalDateTime.now();
            coupon1.setIssueBeginTime(beginTime);
            coupon1.setStatus(CouponStatus.ISSUING);
        } else {
            coupon1.setStatus(CouponStatus.UN_ISSUE);
        }

        //4.开始更新优惠券
        updateById(coupon1);

        //5.兑换码生成
        //5.1判断是否需要生成验证码
        if (coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT) {
            coupon.setIssueEndTime(coupon1.getIssueEndTime());
            exchangeCodeService.asyncGenerateCode(coupon);
        }
    }

    /**
     * 修改优惠券
     *
     * @param dto
     */
    @Override
    @Transactional
    public void updateCoupon(CouponFormDTO dto) {
        //1.先根据id查询
        Coupon coupon = this.getById(dto.getId());
        if (ObjectUtils.isEmpty(coupon)) {
            throw new BadRequestException("优惠券不存在");
        }
        //2.判断优惠券状态,只有待发放
        if (coupon.getStatus() != CouponStatus.DRAFT) {
            throw new BadRequestException("优惠券状态不正确");
        }
        //3.数据拷贝
        Coupon coupon1 = BeanUtils.copyBean(dto, Coupon.class);
        //4.修改数据库
        updateById(coupon1);
        //5.修改限定范围表
        if (!dto.getSpecific()) {
            return;
        }
        List<Long> scopes = dto.getScopes();
        if (CollectionUtils.isEmpty(scopes)) {
            throw new BadRequestException("优惠券限定范围不能为空");
        }
        //先删除限定表
        couponScopeService.removeByCouponId(coupon1.getId());

        //新增限定表
        ArrayList<CouponScope> list = new ArrayList<>(scopes.size());
        for (Long scope : scopes) {
            CouponScope couponScope = new CouponScope();
            //属性填充
            couponScope.setCouponId(coupon1.getId());
            couponScope.setBizId(scope);
            list.add(couponScope);
        }
        couponScopeService.saveBatch(list);
    }

    /**
     * 删除优惠券
     *
     * @param id
     */
    @Override
    @Transactional
    public void removeCouponById(Long id) {
        //1.先查询状态
        Coupon coupon = this.getById(id);
        if(!(coupon.getStatus() == CouponStatus.DRAFT)){
            throw new BizIllegalException("优惠券状态不正确");
        }
        //1.删除优惠券
        removeById(id);
        //2.删除限定范围表
        couponScopeService.removeByCouponId(id);
    }

    /**
     * 根据id查询优惠券
     *
     * @param id
     * @return
     */
    @Override
    public CouponDetailVO getCouponById(Long id) {
        //1.查询优惠券
        Coupon coupon = this.getById(id);

        //2.查询限定范围
        //2.1封装限定id集合
        List<CouponScope> list = couponScopeService.lambdaQuery()
                .eq(CouponScope::getCouponId, id)
                .list();
        //2.2收集id集合
        List<Long> collect = list.stream().map(CouponScope::getBizId).collect(Collectors.toList());

        //2.3调用课程接口查询课程信息名字
        List<CategoryBasicDTO> categoryBasicDTOS = categoryCache.queryCategoryByIds(collect);

        //3.封装返回数据
        //3.1封装vo
        CouponDetailVO vo = BeanUtils.copyBean(coupon, CouponDetailVO.class);

        //3.2封装scopes
        List<CouponScopeVO> lists = BeanUtils.copyList(categoryBasicDTOS, CouponScopeVO.class);
        vo.setScopes(lists);

        return vo;
    }

    /**
     * 暂停发放优惠券
     *
     * @param id
     */
    @Override
    public void pauseIssue(Long id) {
        //1.查询该优惠券判断状态是否是进行中
        Coupon coupon = this.getById(id);
        if (coupon.getStatus() != CouponStatus.ISSUING) {
            throw new BadRequestException("优惠券状态不正确");
        }
        //2.修改优惠券状态
        coupon.setStatus(CouponStatus.PAUSE);
        //3.修改数据库
        updateById(coupon);
    }

    /**
     * 查询发放中的优惠券
     *
     * @return
     */
    @Override
    public List<CouponVO> receiveCoupon() {
        //1.查询发放中手动发放的优惠券
        List<Coupon> coupons = this.lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();

        //2.统计当前用户是否已经领取优惠券
        //2.1收集couponid
        List<Long> couponIds =  coupons.stream().map(Coupon::getId).collect(Collectors.toList());
        //2.2查询该用户已领取的优惠券信息
        Long user = UserContext.getUser();
        List<UserCoupon> userCoupons = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, user)
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        //2.3用stream流过滤出该用户是否可以使用该优惠券
        Map<Long, Long> userHaving = userCoupons.stream()
                .filter(userCoupon -> userCoupon.getStatus().equals(UserCouponStatus.UNUSED))
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //2.4用stream流收集用户领取优惠券的数量
        Map<Long, Long> userHaved = userCoupons.stream()
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));

        //3.封装返回数据
        ArrayList<CouponVO> couponVOS = new ArrayList<>(coupons.size());
        for (Coupon coupon : coupons) {
            //3.1拷贝数据
            CouponVO couponVO = BeanUtils.copyBean(coupon, CouponVO.class);
            //3.2判断是否可以使用
            couponVO.setReceived(userHaving.getOrDefault(coupon.getId(), 0L)>0);
            //3.3判断是否可以领取
            couponVO.setAvailable(userHaved.getOrDefault(coupon.getId(), 0L) < coupon.getUserLimit()
                    && coupon.getIssueNum() < coupon.getTotalNum());
            couponVOS.add(couponVO);
        }
        return couponVOS;
    }


}
